﻿using CloudStorageLight.Core;
using DeltaCompressionDotNet.MsDelta;
using DropNet;
using Ionic.Zip;
using Microsoft.InformationProtectionAndControl;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CloudStorageLight.Core.Web
{
    /// <summary>
    /// Web経由のダウンロード・アップロード コントローラ
    /// </summary>
    public abstract partial class FilesControllerBase : Controller
    {
        protected virtual BlobUser GetCurrentUser()
        {
            if (user == null) user = new BlobUser(System.Web.HttpContext.Current.User);
            return user;
        }
        private BlobUser user;

        protected virtual string GetUserEncryptedFileProtectedPassword(BlobService svc, BlobUser u)
        {
            var info = svc.GetUserInfo(u);
            return info == null ? null : info.EncryptedFileProtectedPassword;
        }


        protected abstract BlobService CreateBlobService();

        /// <summary>
        /// ファイル操作
        /// </summary>
        /// <param name="id"></param>
        /// <param name="query"></param>
        /// <param name="passcode"></param>
        /// <param name="attachment"></param>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public virtual async Task<ActionResult> Index(string id, string snapshot, string mode, string baseVersion, string passcode, FormCollection fc)
        {
            if (Request.HttpMethod != "GET" && Request.HttpMethod != "POST") return new HttpStatusCodeResult(405);

            //IDの正規化
            if (Request.QueryString.AllKeys.Contains("id"))
            {
                id = Request.QueryString["id"].TrimStart('/');
            }

            id = "/" + id;

            var user = GetCurrentUser();

            //ゲストアクセス
            if (user.IsGuest)
            {
                using (var svc = CreateBlobService())
                {
                    var filePasscode = await svc.GetFilePasscodeAsync("/" + id.TrimStart('/'));
                    if (!string.IsNullOrEmpty(filePasscode))
                    {
                        if (passcode == null && Request.Cookies["passcode"] != null) passcode = Request.Cookies["passcode"].Value;

                        if (filePasscode != passcode)
                        {
                            return View("PassCode");
                        }
                        else
                        {
                            Response.Cookies.Add(new HttpCookie("passcode", filePasscode));
                        }
                    }
                }
            }

            using (var svc = CreateBlobService())
            {
                //カスタム認証ページの表示
                var customAuth = svc.GetCustomAuth(id);
                if (customAuth != null)
                {
                    if (!customAuth.HasCredentials)
                    {
                        if (customAuth.AuthMode == "immediate")
                        {
                            var cookie = Request.Cookies.Get(customAuth.Key);
                            if (cookie != null && !string.IsNullOrEmpty(cookie.Value))
                            {
                                customAuth.SetAuthInfo(cookie.Value);
                            }
                            if (fc.AllKeys.Contains("user"))
                            {
                                customAuth.User = fc["user"];
                                customAuth.Password = fc["password"];
                                customAuth.Expires = DateTime.UtcNow.AddDays(3);
                                if (!customAuth.HasCredentials)
                                {
                                    return View("UserPassword", (object)customAuth.Name);
                                }
                            }
                            svc.SetCustomAuth(customAuth);
                            try
                            {
                                var work = await svc.ListFilesAsync(user, id, 1);
                            }
                            catch (BlobException bex)
                            {
                                customAuth.User = null;
                                svc.SetCustomAuth(customAuth);
                                if (bex.Code == BlobExceptionCode.InvalidCustomCredentials)
                                {
                                    return View("UserPassword", (object)customAuth.Name);
                                }
                                throw;
                            }
                            Response.Cookies[customAuth.Key].Value = customAuth.GetAuthString();
                        }
                        else if (customAuth.AuthMode == "login")
                        {
                            var info = svc.GetUserInfo(user);
                            if (info == null) throw new Exception("invalid userinfo");
                            passcode = CryptUtil.DecryptStringAES(info.EncryptedFileProtectedPassword, SystemSettings.Instance.GeneralEncryptKey);
                            customAuth.User = user.Name.Split('@')[0];
                            customAuth.Password = passcode;
                            customAuth.Expires = DateTime.UtcNow.AddDays(3);
                            svc.SetCustomAuth(customAuth);
                        }
                        else
                        {
                            throw new NotImplementedException();
                        }
                    }
                }
                try
                {
                    if (Request.Files.Count > 0)
                    {
                        //フォルダアクセス
                        return await UploadFile(user, svc, id, baseVersion);
                    }

                    if (id.EndsWith("/"))
                    {
                        //フォルダアクセス
                        return await Directory(user, svc, id);
                    }
                    else
                    {
                        return await Download(user, svc, id, mode, snapshot, baseVersion);
                    }
                }
                catch (BlobException bex)
                {
                    if (bex.Code == BlobExceptionCode.InvalidCustomCredentials)
                    {
                        if (customAuth != null)
                        {
                            Response.Cookies[customAuth.Key].Value = "";
                        }
                        return View("UserPassword");
                    }
                    throw;
                }
            }

        }

        /// <summary>
        /// ダウンロードメイン処理
        /// </summary>
        /// <param name="id"></param>
        /// <param name="mode"></param>
        /// <param name="snapshot"></param>
        /// <param name="baseVersion"></param>
        /// <returns></returns>
        protected virtual async Task<ActionResult> Download(BlobUser user, BlobService svc, string id, string mode, string snapshot, string baseVersion)
        {
            //パスの存在チェック
            var exist = await svc.ExistsAsync(id);
            if (!exist) return HttpNotFound();
            var file = await svc.GetFileAsync(user, id);

            //チューニング
            //if (!await svc.CanReadAsync(user, id)) throw new BlobException(BlobExceptionCode.NotAuthorization);

            //ファイルアクセス
            Response.Clear();

            this.HttpContext.Items["Download"] = true;

            Response.SetCookie(new HttpCookie("fileDownload", "true") { Path = "/" });
            if (mode == "zip")
            {
                await DownloadAsZip(id, snapshot, user, svc, file);
            }
            else
            {
                await Download(id, mode, snapshot, user, svc, baseVersion, file);
            }
            return new EmptyResult();
        }

        /// <summary>
        /// ZIP形式でのダウンロード
        /// パスコードの設定があればパスワードを設定
        /// </summary>
        /// <param name="id"></param>
        /// <param name="snapshot"></param>
        /// <param name="user"></param>
        /// <param name="svc"></param>
        /// <returns></returns>
        private async Task DownloadAsZip(string id, string snapshot, BlobUser user, BlobService svc, BlobFile blobFile)
        {
            if (!string.IsNullOrEmpty(snapshot) && snapshot.ToLower() != "current") throw new NotSupportedException("zip shanpshot mode");
            string tempzip = null;
            try
            {
                tempzip = await WebUtil.CreateZipFile(svc, user, new List<string>(new string[] { id }));
                using (var source = new FileStream(tempzip, FileMode.Open))
                {
                    Response.ContentType = "application/octet-stream";
                    var fileName = BlobItem.GetFileName(id) + ".zip";
                    AddAttachmentHeader(fileName);
                    await source.CopyToAsync(Response.OutputStream);
                }
            }
            finally
            {
                if (tempzip != null) System.IO.File.Delete(tempzip);
            }


            //var tempzip = Path.GetTempFileName();
            //var temp = Path.Combine(SystemSettings.Instance.WorkFolder, Guid.NewGuid().ToString() + Path.GetExtension(id));
            //string tempRms = null;
            //try
            //{
            //    using (var zip = new ZipFile())
            //    {
            //        zip.AlternateEncoding = Encoding.UTF8;
            //        zip.AlternateEncodingUsage = ZipOption.AsNecessary;

            //        using (var source = new FileStream(temp, FileMode.Create))
            //        {
            //            if (snapshot != null)
            //            {
            //                DateTimeOffset? snapshotTime = null;
            //                if (snapshot.ToLower() == "current")
            //                {
            //                    var file = await EnsureArchiveAsync(user, id, svc);
            //                    snapshotTime = file != null ? file.LatestSnapshot : null;
            //                }
            //                else
            //                {
            //                    snapshotTime = DateTimeOffset.Parse(snapshot);
            //                }
            //                if (snapshotTime == null)
            //                {
            //                    await svc.DownloadFileAsync(user, id, source);

            //                }
            //                else
            //                {
            //                    await svc.DownloadArchiveAsync(user, id, snapshotTime.Value, source);
            //                }
            //            }
            //            else
            //            {
            //                await svc.DownloadFileAsync(user, id, source);
            //            }
            //            source.Flush();
            //        }


            //        var protectedMode = await svc.GetProtectedModeAsync(id);

            //        var passcode = await svc.GetFilePasscodeAsync(id);
            //        var userPasscode = GetUserEncryptedFileProtectedPassword(svc, user);
            //        if (protectedMode.EndsWith("UserPassword") && !string.IsNullOrEmpty(userPasscode))
            //        {
            //            passcode = CryptUtil.DecryptStringAES(userPasscode, SystemSettings.Instance.GeneralEncryptKey);
            //        }

            //        if (protectedMode == "Password" || protectedMode == "UserPassword")
            //        {
            //            if (IsOfficeFilePasscodeOrPdf(id))
            //            {
            //                if (IsPdf(id))
            //                {
            //                    if (!PdfCryptoUtil.IsPasswordProtected(temp))
            //                    {
            //                        PdfCryptoUtil.SetPassword(temp, passcode);
            //                    }
            //                }
            //                else if (!MsOffCryptoUtil.IsEncryptedPackage(temp))
            //                {
            //                    MsOffCryptoUtil.SetPassword(temp, passcode);
            //                }
            //            }
            //        }
            //        else if (protectedMode == "RMS")
            //        {
            //            if (!RmsUtil.IsFileEncrypted(temp))
            //            {
            //                if (!MsOffCryptoUtil.IsRMSProtectedFilename(id))
            //                {
            //                    tempRms = await MsOffCryptoUtil.EncryptRmsFile(svc, id, temp);
            //                    temp = tempRms;
            //                }
            //                else
            //                {
            //                    throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
            //                }
            //            }
            //            else
            //            {
            //                throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
            //            }
            //        }

            //        var e = zip.AddFile(temp);
            //        e.FileName = Path.GetFileNameWithoutExtension(BlobItem.GetFileName(id)) + Path.GetExtension(temp);

            //        var lastModified = blobFile.LastWriteTime ?? blobFile.LastModified;
            //        if (lastModified.HasValue) e.LastModified = lastModified.Value.DateTime;

            //        var creationTime = blobFile.CreationTime ?? blobFile.LastModified;
            //        if (creationTime.HasValue) e.CreationTime = creationTime.Value.DateTime;


            //        if (IsPasswordProtectMode(protectedMode))
            //        {
            //            if (passcode == null) throw new InvalidOperationException("passcode is null");
            //            e.Password = passcode;
            //        }

            //        zip.Save(tempzip);
            //    }

            //    using (var source = new FileStream(tempzip, FileMode.Open))
            //    {
            //        Response.ContentType = "application/octet-stream";
            //        var fileName = BlobItem.GetFileName(id) + ".zip";
            //        AddAttachmentHeader(fileName);
            //        await source.CopyToAsync(Response.OutputStream);
            //    }
            //}
            //finally
            //{
            //    if (System.IO.File.Exists(temp)) System.IO.File.Delete(temp);
            //    System.IO.File.Delete(tempzip);
            //    if (tempRms != null && System.IO.File.Exists(tempRms)) System.IO.File.Delete(tempRms);
            //}
        }


        /// <summary>
        /// 通常ファイル形式のダウンロード
        /// </summary>
        /// <param name="id"></param>
        /// <param name="mode"></param>
        /// <param name="snapshot"></param>
        /// <param name="user"></param>
        /// <param name="svc"></param>
        /// <param name="baseVersion"></param>
        /// <returns></returns>
        protected async Task Download(string id, string mode, string snapshot, BlobUser user, BlobService svc, string baseVersion, BlobFile blobFile)
        {
            DateTimeOffset snapshotTime;
            DateTimeOffset? lastWriteTime = blobFile.LastWriteTime ?? blobFile.LastModified;
            DateTimeOffset? creationTime = blobFile.CreationTime ?? blobFile.LastModified;
            if (snapshot == "no") snapshot = null; //SNAPSHOTを利用しない明示的な指定
            if (snapshot != null && (snapshot.ToLower() == "current" || DateTimeOffset.TryParse(snapshot, out snapshotTime)))
            {
                //SNAPSHOTダウンロード
                await DownloadAsSnapshot(id, mode, snapshot, user, svc, baseVersion, blobFile, lastWriteTime, creationTime);
            }
            else
            {

                var protectedMode = await svc.GetProtectedModeAsync(id);

                //単純ダウンロード
                if (protectedMode == "None")
                {
                    await WriteResponseHeader(id, mode, user, svc, lastWriteTime, creationTime);
                    await svc.DownloadFileAsync(GetCurrentUser(), id, Response.OutputStream);
                }
                if (IsPasswordProtectMode(protectedMode))
                {
                    if (!IsZipPasswordProtectMode(protectedMode) && IsOfficeFilePasscodeOrPdf(id))
                    {
                        //OfficeまたはPDFのパスワード保護
                        var passcode = await svc.GetFilePasscodeAsync(id);
                        var userPasscode = GetUserEncryptedFileProtectedPassword(svc, user);
                        if (protectedMode == "UserPassword" && !string.IsNullOrEmpty(userPasscode))
                        {
                            passcode = CryptUtil.DecryptStringAES(userPasscode, SystemSettings.Instance.GeneralEncryptKey);
                        }
                        var temp = Path.GetTempFileName();
                        try
                        {
                            using (var source = new FileStream(temp, FileMode.Create))
                            {
                                await svc.DownloadFileAsync(GetCurrentUser(), id, source);
                            }
                            if (IsPdf(id))
                            {
                                if (!PdfCryptoUtil.IsPasswordProtected(temp))
                                {
                                    PdfCryptoUtil.SetPassword(temp, passcode);
                                }
                                else
                                {
                                    throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
                                }
                            }
                            else if (!MsOffCryptoUtil.IsEncryptedPackage(temp))
                            {
                                MsOffCryptoUtil.SetPassword(temp, passcode);
                            }
                            else
                            {
                                throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
                            }
                            using (var source = new FileStream(temp, FileMode.Open))
                            {
                                await WriteResponseHeader(id, mode, user, svc, lastWriteTime, creationTime);
                                await source.CopyToAsync(Response.OutputStream);
                            }
                        }
                        finally
                        {
                            System.IO.File.Delete(temp);
                        }
                    }
                    else
                    {
                        //ZIPパスワードモード
                        if (mode != "attachment")
                        {
                            throw new HttpException(403, Messages.MsgNotPasscodeAccess);
                        }
                        else
                        {
                            await DownloadAsZip(id, snapshot, user, svc, blobFile);
                        }
                    }
                }
                if (protectedMode == "RMS")
                {
                    var temp = Path.Combine(SystemSettings.Instance.WorkFolder, Guid.NewGuid().ToString() + Path.GetExtension(id));
                    string rmsTemp = null;
                    try
                    {
                        using (var source = new FileStream(temp, FileMode.Create))
                        {
                            await svc.DownloadFileAsync(GetCurrentUser(), id, source);
                        }
                        if (!RmsUtil.IsFileEncrypted(temp))
                        {
                            if (!MsOffCryptoUtil.IsRMSProtectedFilename(id))
                            {
                                rmsTemp = await MsOffCryptoUtil.EncryptRmsFile(svc, id, temp);
                                if (rmsTemp != temp && mode != "attachment")
                                {
                                    throw new HttpException(403, Messages.MsgNotPasscodeAccess);
                                }
                            }
                            else
                            {
                                throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
                            }
                        }
                        else
                        {
                            rmsTemp = temp;
                        }

                        using (var source = new FileStream(rmsTemp, FileMode.Open))
                        {
                            await WriteResponseHeader(id, mode, user, svc, lastWriteTime, creationTime, Path.GetFileNameWithoutExtension(BlobItem.GetFileName(id)) + Path.GetExtension(rmsTemp));
                            await source.CopyToAsync(Response.OutputStream);
                        }
                    }
                    finally
                    {
                        if (System.IO.File.Exists(temp))System.IO.File.Delete(temp);
                        if (rmsTemp != null && System.IO.File.Exists(rmsTemp)) System.IO.File.Delete(rmsTemp);
                    }
                }
            }
        }

        /// <summary>
        /// スナップショットのダウンロード（キャッシュおよび差分ダウンロードも含まれる）
        /// </summary>
        /// <param name="id"></param>
        /// <param name="mode"></param>
        /// <param name="snapshot"></param>
        /// <param name="user"></param>
        /// <param name="svc"></param>
        /// <param name="baseVersion"></param>
        /// <returns></returns>
        private async Task DownloadAsSnapshot(string id, string mode, string snapshot, BlobUser user, BlobService svc, string baseVersion, BlobFile blobFile, DateTimeOffset? lastWriteTime = null, DateTimeOffset? creationTime = null)
        {
            DateTimeOffset? snapshotTime = null;
            if (snapshot.ToLower() == "current")
            {
                var file = await EnsureArchiveAsync(user, id, svc);
                snapshotTime = file != null ? file.LatestSnapshot : null;
            }
            else
            {
                snapshotTime = DateTimeOffset.Parse(snapshot);
            }

            var protectedMode = await svc.GetProtectedModeAsync(id);

            if (protectedMode == "None")
            {
                //パスコードなし
                if (mode == "delta" && snapshotTime != null)
                {
                    //差分
                    var tempDist = Path.GetTempFileName();
                    var tempSource = Path.GetTempFileName();
                    var tempDelta = Path.GetTempFileName();
                    try
                    {

                        using (var source = new FileStream(tempDist, FileMode.Create))
                        {
                            await svc.DownloadArchiveAsync(user, id, snapshotTime.Value, source);
                        }
                        using (var source = new FileStream(tempSource, FileMode.Create))
                        {
                            var baseVersionTime = DateTimeOffset.Parse(baseVersion);
                            await svc.DownloadArchiveAsync(user, id, baseVersionTime, source);
                        }
                        var compression = new MsDeltaCompression();
                        compression.CreateDelta(tempSource, tempDist, tempDelta);
                        using (var source = new FileStream(tempDelta, FileMode.Open))
                        {
                            Response.ContentType = "application/octet-stream";
                            var fileName = BlobItem.GetFileName(id) + ".patch";
                            AddAttachmentHeader(fileName);
                            if (lastWriteTime.HasValue) Response.Headers.Add("LastWriteTime", lastWriteTime.Value.ToString("O"));
                            if (creationTime.HasValue) Response.Headers.Add("CreationTime", creationTime.Value.ToString("O"));
                            if (snapshotTime != null) Response.Headers.Add("SnapshotTime", snapshotTime.Value.ToString("O"));
                            await source.CopyToAsync(Response.OutputStream);
                        }
                    }
                    finally
                    {
                        System.IO.File.Delete(tempDist);
                        System.IO.File.Delete(tempSource);
                        System.IO.File.Delete(tempDelta);
                    }
                }
                else
                {
                    await WriteResponseHeader(id, mode, user, svc, lastWriteTime, creationTime);
                    if (snapshotTime != null)
                    {
                        Response.Headers.Add("SnapshotTime", snapshotTime.Value.ToString("O"));
                        await svc.DownloadArchiveAsync(user, id, snapshotTime.Value, Response.OutputStream);
                    }
                    else
                    {
                        await svc.DownloadFileAsync(user, id, Response.OutputStream);
                    }
                }
            }

            if (IsPasswordProtectMode(protectedMode))
            {
                var passcode = await svc.GetFilePasscodeAsync(id);
                var userPasscode = GetUserEncryptedFileProtectedPassword(svc, user);

                if (protectedMode.EndsWith("UserPassword") && !string.IsNullOrEmpty(userPasscode))
                {
                    passcode = CryptUtil.DecryptStringAES(userPasscode, SystemSettings.Instance.GeneralEncryptKey);
                }

                if (!IsZipPasswordProtectMode(protectedMode) && IsOfficeFilePasscodeOrPdf(id))
                {
                    //パスコードありOffice
                    var temp = Path.GetTempFileName();
                    try
                    {
                        using (var source = new FileStream(temp, FileMode.Create))
                        {
                            if (snapshotTime != null)
                            {
                                await svc.DownloadArchiveAsync(user, id, snapshotTime.Value, source);
                            }
                            else
                            {
                                await svc.DownloadFileAsync(user, id, source);
                            }
                        }
                        if (IsPdf(id))
                        {
                            if (!PdfCryptoUtil.IsPasswordProtected(temp))
                            {
                                PdfCryptoUtil.SetPassword(temp, passcode);
                            }
                            else
                            {
                                throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
                            }
                        }
                        else if (!MsOffCryptoUtil.IsEncryptedPackage(temp))
                        {
                            MsOffCryptoUtil.SetPassword(temp, passcode);
                        }
                        else
                        {
                            throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));
                        }
                        using (var source = new FileStream(temp, FileMode.Open))
                        {
                            await WriteResponseHeader(id, mode, user, svc, lastWriteTime, creationTime);
                            await source.CopyToAsync(Response.OutputStream);
                        }
                    }
                    finally
                    {
                        System.IO.File.Delete(temp);
                    }
                }
                else
                {
                    if (mode != "attachment")
                    {
                        throw new HttpException(403, Messages.MsgNotPasscodeAccess);
                    }
                    else
                    {
                        await DownloadAsZip(id, snapshot, user, svc, blobFile);
                    }
                }

            }
            if (protectedMode == "RMS")
            {
                //パスコードありOffice
                var temp = Path.Combine(SystemSettings.Instance.WorkFolder, Guid.NewGuid().ToString("D") + Path.GetExtension(id));
                string rmsTemp = null;
                try
                {
                    using (var source = new FileStream(temp, FileMode.Create))
                    {
                        if (snapshotTime != null)
                        {
                            await svc.DownloadArchiveAsync(user, id, snapshotTime.Value, source);
                        }
                        else
                        {
                            await svc.DownloadFileAsync(user, id, source);
                        }
                    }
                    if (!RmsUtil.IsFileEncrypted(temp))
                    {
                        if (!MsOffCryptoUtil.IsRMSProtectedFilename(id))
                        {
                            rmsTemp = await MsOffCryptoUtil.EncryptRmsFile(svc, id, temp);
                            if (rmsTemp != temp && mode != "attachment")
                            {
                                throw new HttpException(403, Messages.MsgNotPasscodeAccess);
                            }
                        }
                        else
                        {
                            throw new BlobException(BlobExceptionCode.CanNotProtect, BlobItem.GetFileName(id));

                        }
                    }
                    else
                    {
                        rmsTemp = temp;
                    }
                    using (var source = new FileStream(rmsTemp, FileMode.Open))
                    {
                        await WriteResponseHeader(id, mode, user, svc, lastWriteTime, creationTime, Path.GetFileNameWithoutExtension(BlobItem.GetFileName(id)) + Path.GetExtension(rmsTemp));
                        await source.CopyToAsync(Response.OutputStream);
                    }
                }
                finally
                {
                    if (System.IO.File.Exists(rmsTemp)) System.IO.File.Delete(rmsTemp);
                }

            }

        }


        /// <summary>
        /// SNAPSHOTバージョンを確保する
        /// </summary>
        /// <param name="user"></param>
        /// <param name="id"></param>
        /// <param name="svc"></param>
        /// <returns></returns>
        private async Task<BlobFile> EnsureArchiveAsync(BlobUser user, string id, BlobService svc)
        {
            //元ファイル
            var fileItem = await svc.GetFileAsync(user, id);


            if (!fileItem.IsArchibveDirty)
            {
                //キャッシュを利用
                return fileItem;
            }
            else
            {
                var latestItem = await svc.EnsureArchiveAsync(id);
                if (latestItem != null)
                {
                    //キャッシュを作成して利用
                    return latestItem;
                }
                else
                {
                    //キャッシュを使わない
                    return null;
                }
            }
        }


        /// <summary>
        /// ダウンロードする際のヘッダーを設定
        /// </summary>
        /// <param name="id"></param>
        /// <param name="mode"></param>
        /// <param name="user"></param>
        /// <param name="svc"></param>
        /// <returns></returns>
        private async Task WriteResponseHeader(string id, string mode, BlobUser user, BlobService svc, DateTimeOffset? lastWriteTime = null, DateTimeOffset? creationTime = null, string name = null)
        {
            Response.BufferOutput = false;
            var file = await svc.GetBlobItemAsync(user, id);
            if (name == null) name = BlobItem.GetFileName(id);
            if (file.ContentType != null)
            {
                Response.ContentType = file.ContentType;
            }
            else
            {
                Response.ContentType = "application/octet-stream";
                if (name.EndsWith(".html") || name.EndsWith(".htm")) Response.ContentType = "text/html";
                if (name.EndsWith(".txt")) Response.ContentType = "text/plain";
            }
            if (mode == "attachment")
            {
                AddAttachmentHeader(name);

            }
            if (lastWriteTime.HasValue) Response.Headers.Add("LastWriteTime", lastWriteTime.Value.ToString("O"));
            if (creationTime.HasValue) Response.Headers.Add("CreationTime", creationTime.Value.ToString("O"));
        }

        /// <summary>
        /// ダウンロードする際のヘッダーを設定（添付形式）
        /// </summary>
        /// <param name="fileName"></param>
        private void AddAttachmentHeader(string fileName)
        {
            Response.BufferOutput = false;
            string contentDisposition;
            if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
                contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
            else if (Request.Browser.Browser == "Safari")
                contentDisposition = "attachment; filename=" + fileName;
            else
                contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
            Response.AddHeader("Content-Disposition", contentDisposition);
        }


        /// <summary>
        /// Office用のパスワード可能なファイルまたはPDFか？
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        private bool IsOfficeFilePasscodeOrPdf(string id)
        {
            return MsOffCryptoUtil.IsOfficeFilePasscode(id) || IsPdf(id);
        }
        private bool IsPdf(string id)
        {
            return id.EndsWith(".pdf", StringComparison.InvariantCultureIgnoreCase);
        }
        private bool IsPasswordProtectMode(string protectedMode)
        {
            if (protectedMode == "Password" || protectedMode == "UserPassword") return true;
            if (protectedMode == "ZipPassword" || protectedMode == "ZipUserPassword") return true;
            return false;
        }
        private bool IsZipPasswordProtectMode(string protectedMode)
        {
            if (protectedMode == "ZipPassword" || protectedMode == "ZipUserPassword") return true;
            return false;
        }

        protected virtual async Task<ActionResult> Directory(BlobUser user, BlobService svc, string id)
        {
            var exist = await svc.ExistsAsync(id);
            if (!exist) throw new BlobException(BlobExceptionCode.InvalidDirectoryPath);
            var vm = await CreateFilesVM(id, user, svc);
            return View(vm);
        }

        protected virtual async Task<ActionResult> UploadFile(BlobUser user, BlobService svc, string id, string baseVersion = null)
        {
            var names = new List<string>();
            try
            {
                if (svc.CloudAccount.StorageLimitSize.HasValue)
                {
                    var size = WebUtil.GetUsedStoargeSize(svc.CloudAccount.AccountName);
                    if (size.HasValue && svc.CloudAccount.StorageLimitSize.Value < size.Value)
                    {
                        throw new BlobException(BlobExceptionCode.StorageQuotaExceeded);
                    }
                }

                for (int i = 0; i < Request.Files.Count; i++)
                {
                    var file = Request.Files[i];
                    if (SystemSettings.Instance.MaxUploadSize.HasValue && file.ContentLength > SystemSettings.Instance.MaxUploadSize)
                    {
                        throw new BlobException(BlobExceptionCode.ExceedUploadFileSize);
                    }
                    var fileName = BlobUtil.UnescapeDataString(file.FileName);

                    var path = id + Path.GetFileName(fileName);
                    await UploadFileAsync(svc, user, path, file, baseVersion);
                    names.Add(fileName);

                }
                //svc.EntryLog(logid, "Upload exit", id, sw.Elapsed.ToString());
                return Json(new { status = true, data = "" });
            }
            catch (Exception ex)
            {
                svc.EntryLog(user, "Error", id, ex.ToString());
                return Json(new { status = false, data = ex.Message });
            }
        }

        //protected  virtual async Task UploadFileAsync(BlobServiceBase svc, BlobUser user, string path, HttpPostedFileBase file)
        //{
        //    await svc.UploadFileAsync(user, path, file.InputStream, file.ContentType);
        //    await svc.EnqueuUpdateFullTextAsync(user, path, Path.GetFileName(file.FileName), null);
        //}

        protected virtual async Task<FilesVM> CreateFilesVM(string id, BlobUser user, BlobService svc)
        {
            var vm = new FilesVM() { User = user, Current = id };
            vm.CloudAccount = svc.CloudAccount;
            vm.Folder = (BlobDirectory)await svc.GetBlobItemAsync(user, id);
            vm.FolderControl = await svc.CanControlAsync(user, id);
            vm.BackupMode = await svc.IsBackupOnAsync(id);
            vm.HasPasscode = (await svc.GetFilePasscodeAsync(id)) != null;
            vm.DownloadFromOriginalUrl = await svc.IsDownloadFromOriginalUrlAsync(id);
            var customAuth = svc.GetCustomAuth(id);
            vm.HasImmediateCustomAuthInfo = customAuth != null && customAuth.AuthMode == "immediate" && customAuth.HasCredentials;
            if (svc.CloudAccount.StorageLimitSize.HasValue)
            {
                var size = WebUtil.GetUsedStoargeSize(svc.CloudAccount.AccountName);
                if (size.HasValue && svc.CloudAccount.StorageLimitSize.Value * 0.8 < size.Value)
                {
                    vm.NotifyMessage = string.Format(Messages.MsgStorageQuotaWarning, size.Value * 100 / svc.CloudAccount.StorageLimitSize.Value);
                }
            }

            return vm;
        }

        protected virtual async Task UploadFileAsync(BlobService svc, BlobUser user, string path, HttpPostedFileBase file, string baseVersion)
        {


            var tempFile = Path.Combine(SystemSettings.Instance.WorkFolder, Guid.NewGuid().ToString() + Path.GetExtension(path));
            try
            {
                if (!await SaveTempFileAsync(svc, user, path, file, Request["uploadId"], tempFile))
                {
                    return;
                }

                if (!string.IsNullOrEmpty(baseVersion))
                {
                    DateTimeOffset snapshot;
                    if (DateTimeOffset.TryParse(baseVersion, out snapshot))
                    {
                        var baseFile = Path.GetTempFileName();
                        var workFile = Path.GetTempFileName();
                        try
                        {
                            using (var st = new FileStream(baseFile, FileMode.Create))
                            {
                                await svc.DownloadArchiveAsync(user, path, snapshot, st);
                            }
                            var compression = new MsDeltaCompression();
                            compression.ApplyDelta(tempFile, baseFile, workFile);
                            System.IO.File.Copy(workFile, tempFile,true);

                        }
                        finally
                        {
                            System.IO.File.Delete(baseFile);
                            System.IO.File.Delete(workFile);

                        }
                    }

                }

                var protectedMode = await svc.GetProtectedModeAsync(path);

                if (IsPasswordProtectMode(protectedMode))
                {
                    var passcode = await svc.GetFilePasscodeAsync(path);
                    var userPasscode = GetUserEncryptedFileProtectedPassword(svc, user);
                    if (protectedMode.EndsWith("UserPassword") && !string.IsNullOrEmpty(userPasscode))
                    {
                        passcode = CryptUtil.DecryptStringAES(userPasscode, SystemSettings.Instance.GeneralEncryptKey);
                    }

                    if (!string.IsNullOrEmpty(passcode))
                    {
                        if (IsOfficeFilePasscodeOrPdf(path))
                        {
                            var ok = true;
                            try
                            {
                                if (IsPdf(path))
                                {
                                    if (PdfCryptoUtil.IsPasswordProtected(tempFile))
                                    {
                                        PdfCryptoUtil.Decrypto(tempFile, passcode);
                                    }
                                }
                                else if (MsOffCryptoUtil.IsEncryptedPackage(tempFile))
                                {
                                    MsOffCryptoUtil.Decrypto(tempFile, passcode);
                                }
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex.ToString());
                                ok = false;
                            }
                            if (!ok || MsOffCryptoUtil.IsEncryptedPackage(tempFile))
                            {
                                if (await svc.IsDecryptableFileOnlyAsync(path))
                                {
                                    throw new BlobException(BlobExceptionCode.NoDecryptableFile);
                                }
                            }
                        }
                    }
                }
                if (protectedMode == "RMS")
                {
                    var tagetFilename = MsOffCryptoUtil.ConvertFromRmsProtectedFileName(path);
                    if (tagetFilename != null && RmsUtil.IsFileEncrypted(tempFile))
                    {
                        var ok = true;
                        try
                        {
                            var oldext = Path.GetExtension(tempFile);
                            tempFile = DecryptRmsFile(svc, tempFile);
                            var newext = Path.GetExtension(tempFile);
                            path = path.Substring(0, path.Length - oldext.Length) + newext;
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(ex.ToString());
                            ok = false;
                        }
                        if (!ok)
                        {
                            if (await svc.IsDecryptableFileOnlyAsync(path))
                            {
                                throw new BlobException(BlobExceptionCode.NoDecryptableFile);
                            }
                        }
                    }
                    if (MsOffCryptoUtil.IsEncryptedPackage(tempFile))
                    {
                        if (await svc.IsDecryptableFileOnlyAsync(path))
                        {
                            throw new BlobException(BlobExceptionCode.NoDecryptableFile);
                        }
                    }
                }

                DateTimeOffset? lastWriteTime = null;
                if (Request.Headers.AllKeys.Contains("LastWriteTime"))
                {
                    DateTimeOffset work;
                    if (DateTimeOffset.TryParse(Request.Headers["LastWriteTime"], out work)) lastWriteTime = work;
                }
                DateTimeOffset? creationTime = null;
                if (Request.Headers.AllKeys.Contains("CreationTime"))
                {
                    DateTimeOffset work;
                    if (DateTimeOffset.TryParse(Request.Headers["CreationTime"], out work)) creationTime = work;
                }
                var uploaded = await svc.UploadFileAsync(user, path, tempFile, file.ContentType, lastWriteTime, creationTime);

                var uploadedItem = await svc.GetFileAsync(user, uploaded.Path);
                DateTimeOffset? updatedlastWriteTime = uploadedItem.LastWriteTime ?? uploadedItem.LastModified;
                if (updatedlastWriteTime.HasValue) Response.Headers.Add("LastWriteTime", updatedlastWriteTime.Value.ToString("O"));

                Response.Headers.Add("UploadedPath", Uri.EscapeDataString(path));

                if (svc.CloudAccount.CanFullTextSearch && await svc.IsSerachedOnAsync(path))
                {
                    //var fulltext = Extractor.Extract(tempFile);

                    //Crawl対応のファイルでなければ除外
                    if (svc.CloudAccount.GetCrawlableFileExtension().Any(x => path.ToLower().EndsWith(x)))
                    {
                        await svc.EnqueuUpdateFullTextAsync(user, path, Path.GetFileName(path), null);
                    }
                }
            }
            finally
            {
                try
                {
                    if (System.IO.File.Exists(tempFile)) System.IO.File.Delete(tempFile);
                }
                catch { }
            }
        }

        private async Task<bool> SaveTempFileAsync(BlobService svc, BlobUser user, string path, HttpPostedFileBase file, string uploadId, string tempFileName)
        {
            if (SystemSettings.Instance.SingleClientAffinity)
            {
                return await SaveTempFileLocalAsync(svc, user, path, file, uploadId, tempFileName);
            }
            else
            {
                return await SaveTempFileBlobAsync(svc, user, path, file, uploadId, tempFileName);
            }
        }



        private async Task<bool> SaveTempFileLocalAsync(BlobService svc, BlobUser user, string path, HttpPostedFileBase file, string uploadId, string tempFileName)
        {
            var contentRangeString = Request.Headers["Content-Range"];
            //bytes 10000-19999/1157632
            if (string.IsNullOrEmpty(contentRangeString))
            {
                SaveTempFile(file.InputStream, tempFileName);
                return true;
            }

            long totalSize = 0;
            long startPosition = 0;
            long endPosition = 0;

            contentRangeString = contentRangeString.Replace("bytes", "").Trim();
            var contentRange = contentRangeString.Split('/');
            string[] ranges = contentRange[0].Split('-');
            startPosition = long.Parse(ranges[0]);
            endPosition = long.Parse(ranges[1]);
            totalSize = long.Parse(contentRange[1]);

            if (SystemSettings.Instance.MaxUploadSize.HasValue && totalSize > SystemSettings.Instance.MaxUploadSize)
            {
                throw new BlobException(BlobExceptionCode.ExceedUploadFileSize);
            }

            if (uploadId == null) throw new InvalidOperationException("uploadId is null");
            var globalTempFile = Path.Combine(SystemSettings.Instance.WorkFolder, uploadId + "_" + BlobItem.GetFileName(path));

            using (var fs = new FileStream(globalTempFile, FileMode.Append))
            {
                await file.InputStream.CopyToAsync(fs);
                if (fs.Length != endPosition + 1) throw new InvalidOperationException("invalid temp file size"); 
            }

            if (endPosition + 1 >= totalSize)
            {
                System.IO.File.Move(globalTempFile, tempFileName);
                return true;
            }else
            {
                return false;

            }
        }

        private async Task<bool> SaveTempFileBlobAsync(BlobService svc, BlobUser user, string path, HttpPostedFileBase file, string uploadId, string tempFileName)
        {
            var contentRangeString = Request.Headers["Content-Range"];
            //bytes 10000-19999/1157632
            if (string.IsNullOrEmpty(contentRangeString))
            {
                SaveTempFile(file.InputStream, tempFileName);
                return true;
            }

            long totalSize = 0;
            long startPosition = 0;
            long endPosition = 0;

            contentRangeString = contentRangeString.Replace("bytes", "").Trim();
            var contentRange = contentRangeString.Split('/');
            string[] ranges = contentRange[0].Split('-');
            startPosition = long.Parse(ranges[0]);
            endPosition = long.Parse(ranges[1]);
            totalSize = long.Parse(contentRange[1]);

            if (SystemSettings.Instance.MaxUploadSize.HasValue && totalSize > SystemSettings.Instance.MaxUploadSize)
            {
                throw new BlobException(BlobExceptionCode.ExceedUploadFileSize);
            }

            var folderPath = BlobItem.GetDirectoryPath(path);
            var tempHeaderPath = folderPath + "__temp__";
            if (uploadId == null) throw new InvalidOperationException("uploadId is null");
            var baseFilePath = tempHeaderPath + BlobItem.GetFileName(path) + "_" + uploadId + "_";
            var tempFilePath = baseFilePath + string.Format("{0:X16}-{1:X16}", startPosition, endPosition);
            await svc.SaveTempAsync(user, tempFilePath, file.InputStream);

            if (endPosition + 1 >= totalSize)
            {
                using (var fs = new FileStream(tempFileName, FileMode.CreateNew))
                {
                    foreach (var temp in (await svc.ListTempFilesAsync(user, baseFilePath)).OrderBy(x => x.Path))
                    {
                        await svc.LoadTempAsync(user, temp.Path, fs);
                        await svc.DeleteTempAsync(user, temp.Path);
                    }

                    foreach (var temp in (await svc.ListTempFilesAsync(user, tempHeaderPath)).OrderBy(x => x.Path))
                    {
                        if (temp.IsDeleted && temp.LastWriteTime.HasValue && temp.LastWriteTime.Value.AddDays(3) < DateTime.UtcNow)
                        {
                            await svc.DeleteTempAsync(user, temp.Path);
                        }
                    }
                }
                return true;
            }
            return false;
        }

        private string DecryptRmsFile(BlobService svc, string tempFile)
        {
            var sym = new SymmetricKeyCredential();
            sym.AppPrincipalId = svc.CloudAccount.RmsServicePrincipal;
            sym.Base64Key = svc.CloudAccount.RmsSymmetricKey;
            sym.BposTenantId = svc.CloudAccount.TenantId;
            var info = new ConnectionInfo(new Uri(svc.CloudAccount.RmsExtranetUrl), new Uri(svc.CloudAccount.RmsIntranetUrl));
            return RmsUtil.DecryptFile(info, sym, tempFile);
        }



        private string SaveTempFile(Stream st, string tempFile)
        {
            using (var wr = System.IO.File.OpenWrite(tempFile))
            {
                var buffer = new byte[1024 * 1024];
                while (true)
                {
                    var cnt = st.Read(buffer, 0, buffer.Length);
                    if (cnt > 0)
                    {
                        wr.Write(buffer, 0, cnt);
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return tempFile;
        }
    }

}
